package com.jse.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.http.WebSocket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class Http {
	
	public static final int HTTP_CONTINUE = 100;
    public static final int HTTP_OK = 200;
    public static final int HTTP_CREATED = 201;
    public static final int HTTP_ACCEPTED = 202;
    public static final int HTTP_NOT_AUTHORITATIVE = 203;
    public static final int HTTP_NO_CONTENT = 204;
    public static final int HTTP_RESET = 205;
    public static final int HTTP_PARTIAL = 206;
    public static final int HTTP_MULT_CHOICE = 300;
    public static final int HTTP_MOVED_PERM = 301;
    public static final int HTTP_MOVED_TEMP = 302;
    public static final int HTTP_SEE_OTHER = 303;
    public static final int HTTP_NOT_MODIFIED = 304;
    public static final int HTTP_USE_PROXY = 305;
    public static final int HTTP_BAD_REQUEST = 400;
    public static final int HTTP_UNAUTHORIZED = 401;
    public static final int HTTP_PAYMENT_REQUIRED = 402;
    public static final int HTTP_FORBIDDEN = 403;
    public static final int HTTP_NOT_FOUND = 404;
    public static final int HTTP_BAD_METHOD = 405;
    public static final int HTTP_NOT_ACCEPTABLE = 406;
    public static final int HTTP_PROXY_AUTH = 407;
    public static final int HTTP_CLIENT_TIMEOUT = 408;
    public static final int HTTP_CONFLICT = 409;
    public static final int HTTP_GONE = 410;
    public static final int HTTP_LENGTH_REQUIRED = 411;
    public static final int HTTP_PRECON_FAILED = 412;
    public static final int HTTP_ENTITY_TOO_LARGE = 413;
    public static final int HTTP_REQ_TOO_LONG = 414;
    public static final int HTTP_UNSUPPORTED_TYPE = 415;
    public static final int HTTP_INTERNAL_ERROR = 500;
    public static final int HTTP_NOT_IMPLEMENTED = 501;
    public static final int HTTP_BAD_GATEWAY = 502;
    public static final int HTTP_UNAVAILABLE = 503;
    public static final int HTTP_GATEWAY_TIMEOUT = 504;
    public static final int HTTP_VERSION = 505;

    public static String code(int code) {
      switch (code) {
        case HTTP_OK: return " OK";
        case HTTP_CONTINUE: return " Continue";
        case HTTP_CREATED: return " Created";
        case HTTP_ACCEPTED: return " Accepted";
        case HTTP_NOT_AUTHORITATIVE: return " Non-Authoritative Information";
        case HTTP_NO_CONTENT: return " No Content";
        case HTTP_RESET: return " Reset Content";
        case HTTP_PARTIAL: return " Partial Content";
        case HTTP_MULT_CHOICE: return " Multiple Choices";
        case HTTP_MOVED_PERM: return " Moved Permanently";
        case HTTP_MOVED_TEMP: return " Temporary Redirect";
        case HTTP_SEE_OTHER: return " See Other";
        case HTTP_NOT_MODIFIED: return " Not Modified";
        case HTTP_USE_PROXY: return " Use Proxy";
        case HTTP_BAD_REQUEST: return " Bad Request";
        case HTTP_UNAUTHORIZED: return " Unauthorized" ;
        case HTTP_PAYMENT_REQUIRED: return " Payment Required";
        case HTTP_FORBIDDEN: return " Forbidden";
        case HTTP_NOT_FOUND: return " Not Found";
        case HTTP_BAD_METHOD: return " Method Not Allowed";
        case HTTP_NOT_ACCEPTABLE: return " Not Acceptable";
        case HTTP_PROXY_AUTH: return " Proxy Authentication Required";
        case HTTP_CLIENT_TIMEOUT: return " Request Time-Out";
        case HTTP_CONFLICT: return " Conflict";
        case HTTP_GONE: return " Gone";
        case HTTP_LENGTH_REQUIRED: return " Length Required";
        case HTTP_PRECON_FAILED: return " Precondition Failed";
        case HTTP_ENTITY_TOO_LARGE: return " Request Entity Too Large";
        case HTTP_REQ_TOO_LONG: return " Request-URI Too Large";
        case HTTP_UNSUPPORTED_TYPE: return " Unsupported Media Type";
        case HTTP_INTERNAL_ERROR: return " Internal Server Error";
        case HTTP_NOT_IMPLEMENTED: return " Not Implemented";
        case HTTP_BAD_GATEWAY: return " Bad Gateway";
        case HTTP_UNAVAILABLE: return " Service Unavailable";
        case HTTP_GATEWAY_TIMEOUT: return " Gateway Timeout";
        case HTTP_VERSION: return " HTTP Version Not Supported";
        default: return " ";
      }
    }
	
	private static Version VERSION=Version.HTTP_2;
	public static final String CRLF = "\r\n";
	public static int timeout=15000;//超时 毫秒
	
	public static void setGloabalVersion(Number versionNum) {
		if(versionNum.intValue()<2) {
			VERSION=Version.HTTP_1_1;
		}else {
			VERSION=Version.HTTP_2;
		}
	}
	
	public static Object send(Version verion,String method, String url,Object body,int timeout,
			Map<String, String> headers,boolean async,Object type) {
		try {
			HttpRequest.Builder build=HttpRequest.newBuilder(URI.create(url));
			build.timeout(Duration.ofMillis(timeout));
			if(verion==null)verion=VERSION;
			build.version(verion);
			if(headers!=null&&headers.size()>0) {
				headers.forEach((k,v)->{
					build.setHeader(k, v);
				});
				if(!headers.containsKey("User-Agent")) {
					build.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53");
				}
			}else {
				build.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53");
			}
			build.setHeader("referer","http://jse.jdk17.cn");
			BodyPublisher bp=null;
			if(body==null) {
				bp=BodyPublishers.noBody();
			}else if(body instanceof File f) {
				bp=BodyPublishers.ofFile(f.toPath());
			}else if(body instanceof Path p) {
				bp=BodyPublishers.ofFile(p);
			}else if(body instanceof byte[] bs) {
				bp=BodyPublishers.ofByteArray(bs);
			}else if(body instanceof InputStream in) {
				bp=BodyPublishers.ofInputStream(()->in);
			}else if(body instanceof Map<?,?> map) {
				if(headers!=null&&headers.containsKey("Content-Type")
					&&headers.get("Content-Type").startsWith("multipart/form-data; boundary=")) {
					String boundary=headers.get("Content-Type").substring(30).trim();
					Map<String,String> textMap=new HashMap<>();//普通表单参数
					Map<String,File> fileMap=new HashMap<>();
					for(Object k:map.keySet()) {
						Object v=map.get(k);
							if(v ==null) {
								textMap.put(k.toString(),"");
							}else if(v instanceof File f) {
								fileMap.put(k.toString(),f);
							}else {
								textMap.put(k.toString(),v.toString());
							}
					}
					StringBuilder sb=new StringBuilder();
					ByteArrayOutputStream out=new ByteArrayOutputStream();
					textMap.forEach((k,v)->{//写普通表单参数
				        sb.append("--").append(boundary).append(CRLF)//写分隔符--${boundary}，并回车换行
				        //写描述信息：Content-Disposition: form-data; name="参数名"，并两个回车换行
				        .append(String.format("Content-Disposition: form-data; name=\"%s\"",k))
				        .append(CRLF).append(CRLF)
				        .append(v).append(CRLF);//写具体内容：参数值，并回车换行
					});
					out.write(sb.toString().getBytes());
					sb.setLength(0);
					fileMap.forEach((k,v)->{//写文件参数
						try (InputStream in = new FileInputStream(v)) {
							sb.append("--").append(boundary).append(CRLF);
				            String fileName = v.getName();
				            String contentDispositionStr = String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", k, fileName) + CRLF;
				            sb.append(contentDispositionStr);
				            String suffix=Fs.suffix(fileName);
				            String mimeType=Fs.mimeType(fileName,"application/octet-stream");
				            String contentType = "Content-Type: "+mimeType + CRLF + CRLF;
				            sb.append(contentType);
				            out.write(sb.toString().getBytes());
				            sb.setLength(0);
							if(headers.containsKey("base64")//jpg,png,jpeg,psd,gif,bmp,webp
								&&suffix.equals(headers.get("base64"))) {
								String img="data:"+mimeType+";base64,"
								+Imgs.base64(in,suffix);
								out.write(img.getBytes());
							}else {
								out.write(in.readAllBytes());
							}
							out.write(CRLF.getBytes());//回车换行
				        } catch (Exception e) {
				        	e.printStackTrace();
				        }
					});
					out.write((CRLF+"--" + boundary + "--"+CRLF).getBytes());//回车换行
					bp=BodyPublishers.ofInputStream(()->{//转换formData
						return new ByteArrayInputStream(out.toByteArray());
					});
				}else {//非文件传输
					StringBuilder sb=new StringBuilder();//普通类型
					map.forEach((k,v)-> {
						sb.append("&").append(k).append("=")
						.append(v);
					});
					bp=BodyPublishers.ofString(sb.substring(1));
				}
			}else {
				String s=body.toString();
				if(s.length()>0&&s.length()<255&&(s.charAt(0)!='{'||s.charAt(0)!='[')&&(s.charAt(0)=='/'||s.charAt(1)==':')&&Fs.exists(s)) {
					bp=BodyPublishers.ofFile(Path.of(s));
				}else {
					bp=BodyPublishers.ofString(body.toString());
				}
			}
			build.method(method,bp);
			BodyHandler<?> bh = BodyHandlers.ofString();
			if(type==null) {
				bh=BodyHandlers.ofString(StandardCharsets.UTF_8);
			}else if(type instanceof String s) {
				bh=BodyHandlers.ofByteArray();
				if(s.equals("byte")) {
					bh=BodyHandlers.ofByteArray();
				}else if(s.equals("in")){
					bh=BodyHandlers.ofInputStream();
				}else if(s.equals("stream")){
					bh=BodyHandlers.ofLines();
				}else if(s.equals("pub")){
					bh=BodyHandlers.ofPublisher();
				}else if(s.endsWith("/")){
					bh=BodyHandlers.ofFileDownload(Path.of(s));
				}else {
					bh=BodyHandlers.ofString(StandardCharsets.UTF_8);
				}
			}else if(type instanceof Path p) {
				if(Files.isDirectory(p)) {
					bh=BodyHandlers.ofFileDownload(p);
				}else {
					bh=BodyHandlers.ofFile(p);
				}
			}else if(type instanceof File f) {
				if(f.isDirectory()) {
					bh=BodyHandlers.ofFileDownload(f.toPath());
				}else {
					bh=BodyHandlers.ofFile(f.toPath());
				}
			}else if(type instanceof Charset charset){
				bh=BodyHandlers.ofString(charset);
			}else if(type instanceof Consumer c) {//Consumer<Optional<byte[]>> consumer
				bh=BodyHandlers.ofByteArrayConsumer(c);
			}
			if(async) {
				return HttpClient.newHttpClient().sendAsync(build.build(), bh);
			}else {
				return HttpClient.newHttpClient().send(build.build(), bh);
			}
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public static String postJson(String url, String json) {
		Object o=send(VERSION,"POST", url, json, timeout,
				Map.of("Content-Type","application/json;charset=utf-8"),false,null);
		if(o instanceof HttpResponse resp) {
			return resp.body().toString();
		}
		return "";
	}
	
	public static String get(String url) {
		Object o=send(VERSION,"GET",url, null,timeout, null, false,null);
		if(o instanceof HttpResponse<?> resp) {
			return resp.body().toString();
		}
		return "";
	}
	
	public static String post(String url,Object paramOrBody) {
		Object o=send(VERSION,"POST",url, paramOrBody,timeout, null, false,null);
		if(o instanceof HttpResponse<?> resp) {
			return resp.body().toString();
		}
		return "";
	}
	
	public static String post(String url, Map<String, String> header, String body, int readTimeoutMs,
			int connectTimeoutMs) {
		try {
			return request("POST",url,body,connectTimeoutMs, readTimeoutMs,
					"application/x-www-form-urlencoded;charset=utf-8",header,false);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 下载文件
	 * @param url 要下载的连接
	 * @param path (string)保存的路径
	 * @param async 是否异步下载 测试时需要Thread.sleep
	 */
	public static void down(String url,String path,boolean async) {
		send(VERSION,"GET",url, null,timeout,null,async,Path.of(path));
	}
	
	/**
     * 
     * @param method 方法GET POST
     * @param url url
     * @param body 参数
     * @param connectTimeout 链接超时时间
     * @param readTimeout 读取时间
     * @param type 返回类型
     * @param headers header
     * @return
     * @throws SocketTimeoutException
     * @throws IOException
     */
    public static String request(String method,String url,Object body,
            int connectTimeout, int readTimeout, String type,
            Map<String, String> headers,boolean isAsync) throws SocketTimeoutException,
            IOException {
        HttpURLConnection conn = null;
        OutputStream out = null;
        String rsp = null;
        try {
            conn = getConnection(new URL(url),method,type,headers);
            conn.setConnectTimeout(connectTimeout);
            conn.setReadTimeout(readTimeout);
            if(body!=null){
                out = conn.getOutputStream();
                if(body instanceof String) {
                out.write(body.toString().getBytes("UTF-8"));
            	}else if(body.getClass().isArray()) {
            	out.write((byte[])body);
            	}
            }
            rsp = getResponseAsString(conn);
        } finally {
            if (out != null) {
                out.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
            conn = null;
        }
        return rsp;
    }
    
    public static String getResponseAsString(HttpURLConnection conn)
            throws IOException {
        InputStream es = conn.getErrorStream();
        if (es == null) {
            return Io.readString(conn.getInputStream());
        } else {
            String msg = Io.readString(es);
            if (msg!=null&&msg.trim().length()>0) {
                throw new IOException(conn.getResponseCode() + ":"
                        + conn.getResponseMessage());
            } else {
                return msg;
            }
        }
    }
	
	public static String postForm(String url,Map<?,?> param) {
		Object o=send(VERSION,"POST",url, param,timeout,
				Map.of("Content-Type","multipart/form-data; boundary=JseHttppostFormBoundary"),
				false,null);
		if(o instanceof HttpResponse<?> resp) {
			return resp.body().toString();
		}
		return "";
	}
	/**
	 * TODO websocket demo
	 * @param url demo:"ws://localhost:8080/echo"
	 */
	public static void websocket(String url) {
		HttpClient client = HttpClient.newHttpClient();
		WebSocket webSocket = client.newWebSocketBuilder()
			.buildAsync(URI.create(url),new WebSocket.Listener() {
				@Override
				public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
					webSocket.request(1);
					return CompletableFuture.completedFuture(data).thenAccept(System.out::println);
				}
			}).join();
		webSocket.sendText("hello ", false);
		webSocket.sendText("world ",true);
		try {
			TimeUnit.SECONDS.sleep(10);
		} catch (InterruptedException e) {}
		webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").join();
	}
	
	public static int code(URL url) {
    	HttpURLConnection huc = null;
		try {
			huc = (HttpURLConnection) url.openConnection();
			return huc.getResponseCode();
		} catch (IOException e) {e.printStackTrace();
			return 500;
		} finally {
			huc.disconnect();
		}
    }
	
	public static HttpURLConnection getConnection(URL url, String method,
            String ctype, Map<String, String> headers) throws IOException {
        HttpURLConnection conn;
        if ("https".equals(url.getProtocol())) {
            SSLContext ctx;
            try {
                ctx = SSLContext.getInstance("TLS");
                ctx.init(new KeyManager[0],
                        new TrustManager[] { new DefaultTrustManager() },
                        new SecureRandom());
            } catch (Exception e) {
                throw new IOException(e);
            }
            HttpsURLConnection connHttps = (HttpsURLConnection) url
                    .openConnection();
            connHttps.setSSLSocketFactory(ctx.getSocketFactory());
            connHttps.setHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
            conn = connHttps;
        } else {
            conn = (HttpURLConnection) url.openConnection();
        }
        conn.setRequestMethod(method);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setRequestProperty("Accept",
                "text/xml,text/javascript,text/html,application/json");
        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36");
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                conn.setRequestProperty(entry.getKey(), entry.getValue());
            }
        }else {
        	conn.setRequestProperty("Content-Type",ctype);
        }
        return conn;
    }
	public static class DefaultTrustManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }
    }
	
	public static InputStream input(String method,String url,Object body,
            int connectTimeout, int readTimeout, String type,
            Map<String, String> headers,boolean isAsync) throws SocketTimeoutException{
    	 HttpURLConnection conn = null;
         OutputStream out = null;
    	try {
            conn = getConnection(new URL(url),method,type,headers);
            conn.setConnectTimeout(connectTimeout);
            conn.setReadTimeout(readTimeout);
            if(body!=null){
                out = conn.getOutputStream();
                if(body instanceof String) {
                out.write(body.toString().getBytes("UTF-8"));
            	}else if(body.getClass().isArray()) {
            	out.write((byte[])body);
            	}
            }
            return conn.getInputStream();
    	}catch (IOException e) {
			return null;
		}
    }

	public static void main(String[] args) {
		Object o=postJson("http://python.wuzhengai.com/cardiocerebral",
				"{\"type\":\"heart_brain_model\",\"parameter\":{\"age\":24,\"sex\":\"男\",\"durg\":\"[\\\"复方压降片\\\",\\\"激素类药物，如可的松、强的松、地塞米松等\\\",\\\"抗癫痫药物苯妥英钠\\\"]\",\"drink\":\"否\",\"smoke\":\"5年\",\"height\":\"1.9\",\"weight\":\"75.0\",\"heartRate\":\"78\",\"residence\":\"中国南方\",\"waistline\":\"100\",\"bloodOxygen\":\"89\",\"homocysteine\":\"6\",\"irritability\":\"[\\\"药物\\\",\\\"非药物\\\"]\",\"lipoproteina\":\"5\",\"dis_selection\":[\"100\",\"101\",\"102\",\"103\",\"104\"],\"eating_habits\":\"[\\\"高盐饮食\\\",\\\"高糖饮食\\\"]\",\"take_medicine\":\"[\\\"抗心律失常药，如美托洛尔、阿替洛尔、地高辛等\\\"]\",\"echocardiogram\":\"心肌病\",\"family_history\":\"哮喘\",\"cervical_doppler\":\"锁骨下动脉狭窄\",\"systolicPressure\":\"120\",\"totalCholesterol\":\"5\",\"cityOrCountryside\":\"城市\",\"diastolicPressure\":\"80\",\"electrocardiogram\":\"窦性心动过速\",\"atrialFibrillation\":\"是\",\"hypersensitivity_C\":\"5\",\"carotidArteryStenosis\":\"是\",\"fasting_blood_glucose\":\"4\",\"highDensityLipoprotein\":\"8\",\"unilateralLimbWeakness\":\"持续时间10-60分钟\",\"treatedSystolicPressure\":\"110\",\"historyOfVascularDisease\":\"[\\\"糖尿病\\\",\\\"血管疾病\\\",\\\"心血管疾病\\\"]\",\"untreatedSystolicPressure\":\"180\",\"leftVentricularHypertrophy\":\"否\",\"postprandial_blood_glucose\":\"7\",\"doubleTransientIschemicAttackHistory\":\"否\",\"speechDisorderWithoutPhysicalWeakness\":\"否\"}}");
				System.out.println(o);
	}
}
